#region Usings

using System;
using System.Collections.Generic;
using System.Linq;
using System.Reflection;

#endregion

namespace Karma.Framework.Core.Utils
{
    ///<summary>
    /// A series of misc utility methods over Types and reflection.
    ///</summary>
    public class ReflectionUtils
    {
        ///<summary>
        /// Retrieves the method info of a method from a given type
        ///</summary>
        ///<param name="targetType">The type that contains the method</param>
        ///<param name="methodName">The name of the method</param>
        ///<returns></returns>
        public static MethodInfo GetMethod(Type targetType, string methodName)
        {
            var methodToInvoke = targetType.GetMethod(methodName);
            if (methodToInvoke == null)
            {
                foreach (var interfaceType in targetType.GetInterfaces())
                {
                    methodToInvoke = GetMethod(interfaceType, methodName);
                    if (methodToInvoke != null)
                    {
                        break;
                    }
                }
            }
            return methodToInvoke;
        }

        public static object[] CastParameters(MethodInfo mi, object[] baseparams)
        {
            if (baseparams != null)
            {
                var parameters = mi.GetParameters();
                var processparams = new object[baseparams.Length];
                var idx = 0;
                foreach (var parameter in parameters)
                {
                    processparams[idx] = Convert.ChangeType(
                        baseparams[idx++], parameter.ParameterType);
                }
                return processparams;
            }
            return null;
        }

        public static Type[] GetParameterTypes(Type targetType, string methodName)
        {
            var mi = GetMethod(targetType, methodName);
            if (mi == null)
            {
                throw new ArgumentException("Method not found");
            }
            var pi = mi.GetParameters();
            var types = from info in pi
                        select info.ParameterType;

            return types.ToArray();
        }

        public static Type[] GetParameterTypes(Type targetType, string methodName, int numparams)
        {
            var mi = TryGetMethodInfo(targetType, methodName, numparams);
            if (mi == null)
            {
                throw new ArgumentException("Method not found");
            }
            var pi = mi.GetParameters();
            var types = from info in pi
                        select info.ParameterType;

            return types.ToArray();
        }

        public static ConstructorInfo GetDefaultConstructor(Type target)
        {
            return target.GetConstructor(
                BindingFlags.Default, null, new Type[0], new ParameterModifier[0]);
        }

        public static ConstructorInfo GetDefaultGenericConstructor(Type target, Type genericType)
        {
            var genericTarget = target.MakeGenericType(genericType);
            var ctor = genericTarget.GetConstructor(
                BindingFlags.Default, null, new Type[0], new ParameterModifier[0]);
            return ctor;
        }

        public static object CreateGenericInstance(Type target, Type genericType)
        {
            var genericTarget = target.MakeGenericType(genericType);
            var ctors = genericTarget.GetConstructors(BindingFlags.Default | BindingFlags.Public);
            if (ctors == null || ctors.Length <= 0)
            {
                return Activator.CreateInstance(genericTarget);
            }
            var ctor = ctors[0];
            return ctor.Invoke(new object[0]);
        }

        public static IList<PropertyInfo> GetProperties(Type type)
        {
            return type.GetProperties(BindingFlags.NonPublic | BindingFlags.Public |
                                      BindingFlags.Instance | BindingFlags.FlattenHierarchy)
                .ToList();
        }

        public static IList<Attribute> GetAttributes(PropertyInfo property)
        {
            return property.GetCustomAttributes(true).Cast<Attribute>().ToList();
        }

        public static Attribute GetAttribute(MemberInfo info, Type typeOfAttribute)
        {
            var atts = info.GetCustomAttributes(typeOfAttribute, true);
            return atts != null ? (Attribute) atts[0] : null;
        }

        public static PropertyInfo GetProperty(Type type, string property)
        {
            return type.GetProperty(property, BindingFlags.Instance | BindingFlags.Public |
                                              BindingFlags.NonPublic | BindingFlags.FlattenHierarchy);
        }

        public static MethodInfo[] GetMethods(Type targetType)
        {
            var methods = new List<MethodInfo>();
            var typeMethods = targetType.GetMethods();
            if (typeMethods != null && typeMethods.Length > 0)
            {
                methods.AddRange(typeMethods);
            }
            foreach (var interfaceType in targetType.GetInterfaces())
            {
                var methodsFromInterface = GetMethods(interfaceType);
                if (methodsFromInterface != null && methodsFromInterface.Length > 0)
                {
                    methods.AddRange(methodsFromInterface);
                }
            }
            if (targetType.BaseType != null && !targetType.BaseType.Equals(typeof (object)))
            {
                var methodsFromBase = GetMethods(targetType.BaseType);
                if (methodsFromBase != null && methodsFromBase.Length > 0)
                {
                    methods.AddRange(methodsFromBase);
                }
            }

            return methods.ToArray();
        }

        public static MethodInfo TryGetMethodInfo(Type targetType, string method, int numparams)
        {
            var methodInfo = (from m in targetType.GetMethods(BindingFlags.FlattenHierarchy | BindingFlags.Public
                                                              | BindingFlags.InvokeMethod | BindingFlags.IgnoreCase |
                                                              BindingFlags.Instance)
                              where m.Name == method && m.GetParameters().Length == numparams
                              select m).FirstOrDefault();
            if (methodInfo == null)
            {
                foreach (var interfaceType in targetType.GetInterfaces())
                {
                    methodInfo = GetMethod(interfaceType, method);
                    if (methodInfo != null)
                    {
                        break;
                    }
                }
            }
            return methodInfo;
        }
    }
}